/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxCamera.h>
#include <iostream>

const int DEFAULT_WIDTH = 200;
const int DEFAULT_HEIGHT = 200;
const float DEFAULT_ORTHO_DISTANCE = 128;
const float DEFAULT_ORTHO_ZOOM = 0.1f;
const float DEFAULT_MIN_ORTHO_ZOOM = 0.02f;
const float DEFAULT_MAX_ORTHO_ZOOM = 400.0f;
const float DEFAULT_ORTHO_ZOOM_SENSITIVITY = 0.8f;
const float DEFAULT_MIN_RADIUS = 0.15f;
const float DEFAULT_MAX_RADIUS = 128.0f;
const float MAX_ZOOM = 1024.0f;

const static float EPSILON = 1e-8f;
const static float EPSILON_L = 1e-5f;
const static float EPSILON_XL = 1e-4f;

using Sx::Math::ORTHOGRAPHIC;
using Sx::Math::PERSPECTIVE;
using Sx::Math::TOP;
using Sx::Math::BOTTOM;
using Sx::Math::LEFT;
using Sx::Math::RIGHT;
using Sx::Math::FRONT;
using Sx::Math::BACK;
using Sx::Math::FIRST_PERSON;
using Sx::Math::THIRD_PERSON;

Sx::Graphics::Objects::Camera::Camera() {
	this->initialize();
	this->setProjection(this->projection);
}

Sx::Graphics::Objects::Camera::Camera(Projection projection, Perspective perspective) {
	this->initialize();
	this->perspective = perspective;
	this->setProjection(projection);
	if ( projection != ORTHOGRAPHIC ) this->rotate();
}

Sx::Graphics::Objects::Camera::Camera(Projection projection, Perspective perspective, unsigned int width, unsigned int height) {
	this->initialize();
	this->perspective = perspective;
	this->setProjection(projection);
	this->width = width;
	this->height = height;
	if ( projection != ORTHOGRAPHIC ) this->rotate();
}

Sx::Graphics::Objects::Camera::Camera(Projection projection, Perspective perspective, float fov, float nearPlane, float farPlane, int width, int height) {
	this->initialize();
	this->perspective = perspective;
	this->setProjection(projection);
	this->nearPlane = nearPlane;
	this->farPlane = farPlane;
	this->fov = fov;
	this->width = width;
	this->height = height;
	if ( projection != ORTHOGRAPHIC ) this->rotate();
}

Sx::Graphics::Objects::Camera::~Camera() {}

void Sx::Graphics::Objects::Camera::initialize() {
	this->perspective = THIRD_PERSON;
	this->projection = PERSPECTIVE;
	this->invertedY = true;
	this->invertedX = false;
	this->radius = 3.0f;
	this->moveSpeed = 1.0f;
	this->nearPlane = 0.001f;
	this->farPlane = 1000.0f;
	this->eye = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
	this->lookAt = Eigen::Vector3f(0.0f, 0.0f, 0.0f);
	this->up = Eigen::Vector3f(0.0f, 1.0f, 0.0f);
	this->orthoMin = Eigen::Vector3f(-FLT_MAX, -FLT_MAX, -FLT_MAX);
	this->orthoMax = Eigen::Vector3f(FLT_MAX, FLT_MAX, FLT_MAX);
	this->limitOrtho = false;
	this->hRadians = std::numeric_limits<float>::epsilon();
	this->vRadians = std::numeric_limits<float>::epsilon();
	this->fov = 45.0f;
	this->panSpeed = 1.0f;
	this->radiusPanFactor = 0.22f;
	this->orthoZoomSensitivity = DEFAULT_ORTHO_ZOOM_SENSITIVITY;
	this->minimumRadius = DEFAULT_MIN_RADIUS;
	this->maximumRadius = DEFAULT_MAX_RADIUS;
	this->sensitivityX = 0.01f;
	this->sensitivityY = 0.01f;
	this->width = DEFAULT_WIDTH;
	this->height = DEFAULT_HEIGHT;
	this->view = TOP;
	this->orthoZoom = 2;
	this->aspectRatio = (float)((float)this->width / (float)this->height);
	this->orthoWidth = this->orthoZoom;
	this->orthoHeight = this->orthoZoom * this->aspectRatio;
	this->visible = true;
}

bool Sx::Graphics::Objects::Camera::construct() {
	return true;
}

void Sx::Graphics::Objects::Camera::update(float dt) {
	return;
}

void Sx::Graphics::Objects::Camera::setSize(unsigned int width, unsigned int height) {
	this->width = width;
	this->height = height;
	this->aspectRatio = (float)((float)width / (float)height);
	this->orthoHeight = this->orthoZoom;
	this->orthoWidth = this->aspectRatio * this->orthoZoom;
	this->conX = (float)((this->aspectRatio * this->orthoZoom) / this->width);
	this->conY = (float)(this->orthoZoom / (float)this->height);
}

void Sx::Graphics::Objects::Camera::resize(unsigned int width, unsigned int height) {
	this->setSize(width, height);
}

void Sx::Graphics::Objects::Camera::rotate(int xDifference, int yDifference) {
	this->rotate(xDifference * this->sensitivityX, yDifference * this->sensitivityY);
}

void Sx::Graphics::Objects::Camera::scope(int xDifference, int yDifference) {
	if ( this->perspective != FIRST_PERSON ) return;

	this->eye.x() = this->eye.x() + yDifference * this->sensitivityY * this->panSpeed * cos(this->vRadians) * cos(this->hRadians);
	this->eye.z() = this->eye.z() + yDifference * this->sensitivityY * this->panSpeed * cos(this->vRadians) * sin(this->hRadians);

	this->lookAt.x() = this->lookAt.x() + yDifference * this->sensitivityY * this->panSpeed * cos(this->vRadians) * cos(this->hRadians);
	this->lookAt.y() = this->lookAt.y() + yDifference * this->sensitivityY * this->panSpeed * sin(this->vRadians);
	this->lookAt.z() = this->lookAt.z() + yDifference * this->sensitivityY * this->panSpeed * cos(this->vRadians) * sin(this->hRadians);

	this->rotate(xDifference * this->sensitivityX, 0.0f);
}

void Sx::Graphics::Objects::Camera::pan(int xDifference, int yDifference) {
	if ( this->projection == PERSPECTIVE ) {
		if ( this->perspective == THIRD_PERSON )
			this->pan_third_person(xDifference * this->sensitivityX, yDifference * this->sensitivityY);
		else if ( this->perspective == FIRST_PERSON )
			this->pan_first_person(xDifference * this->sensitivityX, yDifference * this->sensitivityY);
		else
			std::cout << "[Camera:pan] Camera perspective error in pan(int, int)" << std::endl;
	}
	else {
		this->pan_orthographic(xDifference, yDifference);
	}
}

void Sx::Graphics::Objects::Camera::pan_third_person(float xAmount, float yAmount) {
	float halfPi = static_cast<float>(M_PI) * 0.5f;
	float lookXPart_X = xAmount * this->panSpeed * this->radius * this->radiusPanFactor * cos(this->hRadians + halfPi);
	float lookYPart_X = yAmount * this->panSpeed * this->radius * this->radiusPanFactor * sin(this->hRadians + halfPi);
	this->lookAt.x() = 
		this->lookAt.x()
		+ (xAmount * this->panSpeed * this->radius * this->radiusPanFactor * cos(this->hRadians + halfPi) * cos(this->vRadians))
		+ ((lookXPart_X - lookYPart_X) * sin(this->vRadians));
			
	this->lookAt.y() = this->lookAt.y() + (yAmount * this->panSpeed * this->radius * this->radiusPanFactor * sin(this->vRadians + halfPi) * cos(this->vRadians));

	float lookXPart_Z = xAmount * this->panSpeed * this->radius * this->radiusPanFactor * sin(this->hRadians+ halfPi);
	float lookYPart_Z = yAmount * this->panSpeed * this->radius * this->radiusPanFactor * cos(this->hRadians+ halfPi);
	this->lookAt.z() =
		this->lookAt.z()
		+ (xAmount * this->panSpeed * this->radius * this->radiusPanFactor * sin(this->hRadians + halfPi) * cos(this->vRadians)) 
		+ ((lookXPart_Z + lookYPart_Z) * sin(this->vRadians));

	this->rotate();
}

void Sx::Graphics::Objects::Camera::strafe_first_person(float amount) {
	float halfPi = static_cast<float>(M_PI) * 0.5f;
	this->eye.x() = this->eye.x() + amount * this->moveSpeed * (cos(this->hRadians + halfPi));
	this->eye.z() = this->eye.z() + amount * this->moveSpeed * (sin(this->hRadians + halfPi));
	this->rotate();
}

void Sx::Graphics::Objects::Camera::elevate_first_person(float amount) {
	Eigen::Vector3f dir = Eigen::Vector3f(0.0f, 1.0f, 0.0f);
	this->eye += dir * amount;
	this->lookAt += dir * amount;
	this->rotate();
}

void Sx::Graphics::Objects::Camera::pan_first_person(float xAmount, float yAmount) {
	this->elevate_first_person(yAmount);
	this->strafe_first_person(xAmount);
	this->rotate();
}

void Sx::Graphics::Objects::Camera::pan_orthographic(int xDifference, int yDifference) {

	if ( this->view == TOP || this->view == BOTTOM ) {
		this->eye.x() = this->eye.x() - (xDifference * this->conX * this->panSpeed);
		this->eye.z() = this->eye.z() - (yDifference * this->conY * this->panSpeed);

		this->lookAt.x() = this->lookAt.x() - (xDifference * this->conX * this->panSpeed);
		this->lookAt.z() = this->lookAt.z() - (yDifference * this->conY * this->panSpeed);

		this->up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
	}
	else if ( this->view == LEFT || this->view == RIGHT ) {
		if ( this->view == RIGHT ) xDifference = -xDifference;

		this->eye.y() = this->eye.y() - (-yDifference * this->conY * this->panSpeed);
		this->eye.z() = this->eye.z() - (-xDifference * this->conX * this->panSpeed);

		this->lookAt.y() = this->lookAt.y() - (-yDifference * this->conY * this->panSpeed);
		this->lookAt.z() = this->lookAt.z() - (-xDifference * this->conX * this->panSpeed);

		this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
	}
	else {
		if ( this->view == FRONT ) xDifference = -xDifference;

		this->eye.x() = this->eye.x() - (-xDifference * this->conX * this->panSpeed);
		this->eye.y() = this->eye.y() - (-yDifference * this->conY * this->panSpeed);

		this->lookAt.x() = this->lookAt.x() - (-xDifference * this->conX * this->panSpeed);
		this->lookAt.y() = this->lookAt.y() - (-yDifference * this->conY * this->panSpeed);

		this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
	}
}

void Sx::Graphics::Objects::Camera::rotate(float hRads, float vRads) {
	if ( this->projection == ORTHOGRAPHIC )
		return;

	float halfPi = static_cast<float>(M_PI) * 0.5f;
	this->hRadians += hRads;
	if ( this->perspective == FIRST_PERSON )
		this->vRadians -= vRads;
	else
		this->vRadians += vRads;

	if ( this->vRadians > 0 - EPSILON_XL && this->vRadians < 0 )
		this->vRadians = -EPSILON_XL;
	if ( this->vRadians < 0 + EPSILON_XL && this->vRadians > 0 )
		this->vRadians = EPSILON_XL;
		
	if ( this->vRadians > halfPi - EPSILON_L )
		this->vRadians = halfPi - EPSILON_L;
	if ( this->vRadians < -halfPi + EPSILON_L)
		this->vRadians = -halfPi  + EPSILON_L;

	if ( this->perspective == FIRST_PERSON )
		this->rotate_first_person();
	else if ( this->perspective == THIRD_PERSON )
		this->rotate_third_person();
	else
		std::cout << "[Camera] Camera perspective error in rotate(float, float)" << std::endl;
}

void Sx::Graphics::Objects::Camera::rotate() {
	this->rotate(0.0f, 0.0f);
}

void Sx::Graphics::Objects::Camera::rotate_first_person() {
	this->lookAt.x() = this->eye.x() + (this->radius * cos(this->vRadians) * cos(this->hRadians));
	this->lookAt.y() = this->eye.y() + (this->radius * sin(this->vRadians));
	this->lookAt.z() = this->eye.z() + (this->radius * cos(this->vRadians) * sin(this->hRadians));
	this->up = Eigen::Vector3f(0.0f, 1.0f, 0.0f);
}

void Sx::Graphics::Objects::Camera::rotate_third_person() {
	this->eye.x() = this->lookAt.x() + (this->radius * cos(this->vRadians) * cos(this->hRadians));
	this->eye.y() = this->lookAt.y() + (this->radius * sin(this->vRadians));
	this->eye.z() = this->lookAt.z() + (this->radius * cos(this->vRadians) * sin(this->hRadians));

	this->up.x() = this->eye.x() - this->lookAt.x();
	if ( this->eye.y() < 0 )
		this->up.y() = -this->eye.y() + (this->radius * sin(this->vRadians));
	else
		this->up.y() = abs(this->eye.y() + (this->radius * sin(this->vRadians)));
	this->up.z() = this->eye.z() - this->lookAt.z();
}

void Sx::Graphics::Objects::Camera::addRadius(float value) {
	if ( this->perspective == FIRST_PERSON )
		return;
	if ( this->radius + value < this->minimumRadius )
		this->radius = this->minimumRadius;
	else if ( this->radius + value > this->maximumRadius )
		this->radius = this->maximumRadius;
	else
		this->radius += value;
	this->rotate();
}

void Sx::Graphics::Objects::Camera::setPosition(float x, float y, float z) {
	this->setPosition(Eigen::Vector3f(x, y, z));
}

void Sx::Graphics::Objects::Camera::setPosition(const Eigen::Vector3f& position) {
	if ( this->projection == ORTHOGRAPHIC ) {
		switch ( this->view ) {
		case TOP:
			this->eye = Eigen::Vector3f(position.x(), DEFAULT_ORTHO_DISTANCE, position.z());
			this->lookAt = Eigen::Vector3f(position.x(), 0.0f, position.z());
			this->up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
		break;
		case BOTTOM: 
			this->eye = Eigen::Vector3f(position.x(), -DEFAULT_ORTHO_DISTANCE, position.z());
			this->lookAt = Eigen::Vector3f(position.x(), 0.0f, position.z());
			this->up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
		break;
		case LEFT: 
			this->eye = Eigen::Vector3f(DEFAULT_ORTHO_DISTANCE, position.y(), position.z());
			this->lookAt = Eigen::Vector3f(0.0f, position.y(), position.z());
			this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
		break;
		case RIGHT: 
			this->eye = Eigen::Vector3f(-DEFAULT_ORTHO_DISTANCE, position.y(), position.z());
			this->lookAt = Eigen::Vector3f(0.0f, position.y(), position.z());
			this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
		break;
		case FRONT: 
			this->eye = Eigen::Vector3f(position.x(), position.y(), DEFAULT_ORTHO_DISTANCE);
			this->lookAt = Eigen::Vector3f(position.x(), position.y(), 0.0f);
			this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
		break;
		case BACK:
			this->eye = Eigen::Vector3f(position.x(), position.y(), -DEFAULT_ORTHO_DISTANCE);
			this->lookAt = Eigen::Vector3f(position.x(), position.y(), 0.0f);
			this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
		break;
		};
	}
	else {
		if ( this->perspective == FIRST_PERSON ) this->eye = position;
		else this->lookAt = position;
	}

	this->rotate(0.0f, 0.0f);
}

void Sx::Graphics::Objects::Camera::setProjection(Projection projection) {
	this->projection = projection;

	if ( this->projection == PERSPECTIVE ) {
		this->eye = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
		this->lookAt = Eigen::Vector3f::Zero();
		this->up = Eigen::Vector3f(0.0f, 1.0f, 0.0f);
		this->orthoZoom = 2;
		this->rotate();
		this->resetRotation();
	}
	else if ( this->projection == ORTHOGRAPHIC ) {
		switch ( this->view ) {
			case TOP:
				this->eye = Eigen::Vector3f(0.0f, DEFAULT_ORTHO_DISTANCE, 0.0f);
				this->up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
			break;
			case BOTTOM:
				this->eye = Eigen::Vector3f(0.0f, -DEFAULT_ORTHO_DISTANCE, 0.0f);
				this->up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
			break;
			case LEFT:
				this->eye = Eigen::Vector3f(DEFAULT_ORTHO_DISTANCE, 0.0f, 0.0f);
				this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
			break;
			case RIGHT:
				this->eye = Eigen::Vector3f(-DEFAULT_ORTHO_DISTANCE, 0.0f, 0.0f);
				this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
			break;
			case FRONT:
				this->eye = Eigen::Vector3f(0.0f, 0.0f, DEFAULT_ORTHO_DISTANCE); 
				this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
			break;
			case BACK:
				this->eye = Eigen::Vector3f(0.0f, 0.0f, -DEFAULT_ORTHO_DISTANCE);
				this->up = Eigen::Vector3f(0.0f, -1.0f, 0.0f);
			break;
			default: this->eye = Eigen::Vector3f(DEFAULT_ORTHO_DISTANCE, 0.0f, 0.0f); break;
		};

		this->lookAt = Eigen::Vector3f::Zero();
		this->orthoZoom = 2.0f;
		this->zoom(0);
	}
	else
		std::cout << "[Camera:setProjection] Projection error int setProjection(Projection)" << std::endl;
}

void Sx::Graphics::Objects::Camera::setPerspective(Perspective perspective) {
	this->perspective = perspective;
	this->rotate();
}

void Sx::Graphics::Objects::Camera::invertX(bool b) {
	this->invertedX = b;
}

void Sx::Graphics::Objects::Camera::invertY(bool b) {
	this->invertedY = b;
}

void Sx::Graphics::Objects::Camera::setView(View view) {
	this->view = view;
	this->setProjection(ORTHOGRAPHIC);
}

Sx::Math::View Sx::Graphics::Objects::Camera::getView() const {
	return this->view;
}

int Sx::Graphics::Objects::Camera::getWidth() const {
	return this->width;
}

int Sx::Graphics::Objects::Camera::getHeight() const {
	return this->height;
}

void Sx::Graphics::Objects::Camera::setOrthoMin(const Eigen::Vector3f& min) {
	this->limitOrtho = true;
	this->orthoMin = min;
}

void Sx::Graphics::Objects::Camera::setOrthoMax(const Eigen::Vector3f& max) {
	this->limitOrtho = true;
	this->orthoMax = max;
}

void Sx::Graphics::Objects::Camera::setOrthoZoomSensitivity(float sensitivity) {
	this->orthoZoomSensitivity = sensitivity;
}

float Sx::Graphics::Objects::Camera::getOrthoLeft() const {
	if ( this->view == TOP || this->view == LEFT || this->view == FRONT || this->view == BACK || this->view == RIGHT )
		return this->orthoWidth / 2.0f;
	return -(this->orthoWidth / 2.0f);
}

float Sx::Graphics::Objects::Camera::getOrthoRight() const {
	if ( this->view == TOP || this->view == LEFT || this->view == FRONT || this->view == BACK || this->view == RIGHT )
		return -(this->orthoWidth / 2.0f);
	return this->orthoWidth / 2.0f;
}

float Sx::Graphics::Objects::Camera::getOrthoTop() const {
	return -(this->orthoHeight / 2.0f);
}

float Sx::Graphics::Objects::Camera::getOrthoBottom() const {
	return this->orthoHeight / 2.0f;
}

float Sx::Graphics::Objects::Camera::getOrthoWidth() const {
	return this->orthoWidth;
}

float Sx::Graphics::Objects::Camera::getOrthoHeight() const {
	return this->orthoHeight;
}

float Sx::Graphics::Objects::Camera::getOrthoZoom() const {
	return this->orthoZoom;
}

float Sx::Graphics::Objects::Camera::getRadius() const {
	return this->radius;
}

float Sx::Graphics::Objects::Camera::getAspect() const {
	return this->aspectRatio;
}

void Sx::Graphics::Objects::Camera::setAspectRatio(float ratio) {
	this->aspectRatio = ratio;
}

void Sx::Graphics::Objects::Camera::setVRadians(float v) {
	this->vRadians = v;
	this->rotate();
}

void Sx::Graphics::Objects::Camera::setHRadians(float h) {
	this->hRadians = h;
	this->rotate();
}

void Sx::Graphics::Objects::Camera::resetRotation() {
	this->hRadians = std::numeric_limits<float>::epsilon();
	this->vRadians = std::numeric_limits<float>::epsilon();
	this->rotate();
}

void Sx::Graphics::Objects::Camera::zoom(float amount) {
	if ( this->projection == PERSPECTIVE ) this->zoom_third_person(amount);
	else this->zoom_orthographic(amount);
}

void Sx::Graphics::Objects::Camera::zoom_third_person(float amount) {
	float radiusFraction = this->radius * 0.30f;
	float newAmount = amount * radiusFraction;
	this->addRadius(-newAmount);
}

float ortho_zoom_amount(float width, float height, float amount, float sensitivity) {
	if ( width > height ) return height * sensitivity * amount;
	else return width * sensitivity * amount;
}

float Sx::Graphics::Objects::Camera::calculate_ortho_zoom(float amount) const {
	if ( this->orthoWidth > this->orthoHeight ) return this->orthoHeight * this->orthoZoomSensitivity * amount;
	else return this->orthoWidth * this->orthoZoomSensitivity * amount;
}

void Sx::Graphics::Objects::Camera::zoom_orthographic(float amount) {
	if ( this->orthoZoom > DEFAULT_MIN_ORTHO_ZOOM ) {
		if ( this->orthoZoom + calculate_ortho_zoom(amount) >= MAX_ZOOM )
			return;
		this->orthoZoom += calculate_ortho_zoom(amount);
	}
	else {
		if ( this->orthoZoom + calculate_ortho_zoom(amount) <= DEFAULT_MIN_ORTHO_ZOOM )
			this->orthoZoom = DEFAULT_MIN_ORTHO_ZOOM;
		if ( this->orthoZoom == DEFAULT_MIN_ORTHO_ZOOM && amount > 0 )
			this->orthoZoom += std::abs(calculate_ortho_zoom(amount));	
	}

	if ( this->orthoZoom <= DEFAULT_MIN_ORTHO_ZOOM )
		this->orthoZoom = DEFAULT_MIN_ORTHO_ZOOM;
	if ( this->orthoZoom >= DEFAULT_MAX_ORTHO_ZOOM )
		this->orthoZoom = DEFAULT_MAX_ORTHO_ZOOM;

	// June 4th: Changed this so that when new windows are opened the OpenGL
	// view will be of the correct size.
	this->setSize(this->width, this->height);
	//this->resize();
}

void Sx::Graphics::Objects::Camera::walk(int xDifference, int yDifference) {
	if ( this->projection == PERSPECTIVE ) {
		if ( this->perspective == FIRST_PERSON ) {
			this->pan_horizontal_clamp(yDifference * this->sensitivityY);
			this->rotate(xDifference * this->sensitivityX, 0.0f);
		}
	}
}

void Sx::Graphics::Objects::Camera::pan_horizontal_clamp(float distance) {
	Eigen::Vector3f dir = this->lookAt - this->eye;
	dir.y() = 0.0f;
	this->eye += dir * (distance * this->moveSpeed);
	this->lookAt += dir * (distance * this->moveSpeed);
	this->rotate();
}

void Sx::Graphics::Objects::Camera::setEye(Eigen::Vector3f eye) {
	this->eye = eye;
}

void Sx::Graphics::Objects::Camera::setLookAt(Eigen::Vector3f lookAt) {
	this->lookAt = lookAt;
}

void Sx::Graphics::Objects::Camera::setUp(Eigen::Vector3f up) {
	this->up = up;
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getEye() const {
	return this->eye;
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getLookAt() const {
	return this->lookAt;
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getUp() const {
	return this->up;
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getViewDirection() const {
	return (this->lookAt - this->eye).normalized();
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getRightDirection() const {
	return (this->lookAt.cross(this->up)).normalized();
}

void Sx::Graphics::Objects::Camera::setMoveSpeed(float speed) {
	this->moveSpeed = speed;
}

void Sx::Graphics::Objects::Camera::setSensitivityX(float sensitivity) {
	this->sensitivityX = sensitivity;
}

void Sx::Graphics::Objects::Camera::setSensitivityY(float sensitivity) {
	this->sensitivityY = sensitivity;
}

void Sx::Graphics::Objects::Camera::setMinimumRadius(float radius) {
	this->maximumRadius = radius;
}

void Sx::Graphics::Objects::Camera::setMaximumRadius(float radius) {
	this->minimumRadius = radius;
}

void Sx::Graphics::Objects::Camera::setFarPlane(float distance) {
	this->farPlane = distance;
}

void Sx::Graphics::Objects::Camera::setNearPlane(float distance) {
	this->nearPlane = distance;
}

void Sx::Graphics::Objects::Camera::setFieldOfView(float angle) {
	this->fov = angle;
}

float Sx::Graphics::Objects::Camera::getMinimumRadius() const {
	return this->minimumRadius;
}

float Sx::Graphics::Objects::Camera::getMaximumRadius() const {
	return this->maximumRadius;
}

float Sx::Graphics::Objects::Camera::getFarPlane() const {
	return this->farPlane;
}

float Sx::Graphics::Objects::Camera::getNearPlane() const {
	return this->nearPlane;
}

float Sx::Graphics::Objects::Camera::getFOV() const {
	return this->fov;
}

float Sx::Graphics::Objects::Camera::getConX() const {
	return this->conX;
}

float Sx::Graphics::Objects::Camera::getConY() const {
	return this->conY;
}

float Sx::Graphics::Objects::Camera::getPanSpeed() const {
	return this->panSpeed;
}

Eigen::Vector3f Sx::Graphics::Objects::Camera::getDirection() const {
	Eigen::Vector3f dir(std::cos(this->hRadians) * this->radius, 0.0f, std::sin(this->hRadians) * this->radius);
	return dir.normalized();
}

Sx::Math::Perspective Sx::Graphics::Objects::Camera::getPerspective() const {
	return this->perspective;
}

Sx::Math::Projection Sx::Graphics::Objects::Camera::getProjection() const {
	return this->projection;
}
